#include "difflist.h"

#include <cstdio>
#include <vector>

#include <wx/dir.h>
#include <wx/filename.h>
#include <wx/arrstr.h>
#include <wx/log.h>
#include <wx/file.h>
#include <wx/wfstream.h>
#include <wx/filefn.h>
#include <wx/msgdlg.h>

#include "FileSyncMain.h"

using namespace std;

class wxDirTraverserSimple : public wxDirTraverser
{
public:
    wxDirTraverserSimple(wxArrayString& files) : m_files(files) { }

    virtual wxDirTraverseResult OnFile(const wxString& filename)
    {
        m_files.Add(filename);
        return wxDIR_CONTINUE;
    }

    virtual wxDirTraverseResult OnDir(const wxString& (dirname))
    {
        m_files.Add(dirname);
        return wxDIR_CONTINUE;
    }

private:
    wxArrayString& m_files;
};


FileDiffList::FileDiffList(wxWindow* parent, wxWindowID id,
                           const wxPoint& pos, const wxSize& size,
                           long style, const wxValidator& validator,
                           const wxString& name):
    wxListCtrl(parent, id, pos, size, style, validator, name)
{
    InsertColumn(1, wxT("源文件列表(0)"));
    InsertColumn(1, wxT("动作"));
    InsertColumn(2, wxT("目标文件列表(0)"));
}

int FileDiffList::Reload(const wxString& dirPath)
{
    DeleteAllItems();
    wxArrayString files;
    wxDir dir(dirPath);
    wxDirTraverserSimple traverser(files);

    dir.Traverse(traverser);
    for( size_t i = 0; i < files.Count(); ++i )
    {
        InsertItem(i, files[i].Mid(dirPath.Len()+1));
        //wxLogMessage(wxT("Reload: InsertItem id = %ld"), index);
    }
    m_srcDir = dirPath;
    wxListItem colItem;
    colItem.SetText(wxString::Format(wxT("源文件列表(%d)"), files.Count()));
    SetColumn(0, colItem);
    return 0;
}

int FileDiffList::LoadDst(const wxString& dstPath)
{
    wxArrayString files;
    wxDir dir(dstPath);
    wxDirTraverserSimple traverser(files);

    dir.Traverse(traverser);
    vector<wxString> toBeDelete;
    for( size_t i = 0; i < files.Count(); ++i )
    {
        wxString name = files[i].Mid(dstPath.Len()+1);
        long index = FindItem(-1, name);
        //wxLogMessage(wxT("%s: FindItem(%s) return %ld"), __func__, name.c_str(), index);
        if(index == wxNOT_FOUND)
        {
            index = InsertItem(GetItemCount(), wxT(""));
            //wxLogMessage(wxT("LoadDst: InsertItem id = %ld"), index);
        }
        SetItem(index, 2, name);
    }
    m_dstDir = dstPath;
    wxListItem colItem;
    colItem.SetText(wxString::Format(wxT("目标文件列表(%d)"), files.Count()));
    SetColumn(2, colItem);

    size_t changeCnt = 0;
    for(int i=0; i < GetItemCount(); ++i)
    {
        if( ChangeStatus(i) ) ++changeCnt;
        //wxLogMessage(wxT("LoadDst: Item(%ld).Text:%s"), i, GetItemText(i).c_str());
    }
    colItem.SetText(wxString::Format(wxT("动作(%d)"), changeCnt));
    SetColumn(1, colItem);
    return 0;
}

bool FileDiffList::ChangeStatus(long itemIndex)
{
    bool hasChange = false;
    wxListItem items[3];
    for(int i=0; i < 3; ++i)
    {
        items[i].SetId(itemIndex);
        items[i].SetColumn(i);
        items[i].SetMask(wxLIST_MASK_TEXT);
        if(!GetItem(items[i]))
        {
            //wxLogMessage(wxT("ChangeStatus: GetItem (%ld, %ld) fail"), itemIndex, i);
        }
        //wxLogMessage(wxT("ChangeStatus: Item(%ld, %ld): %s"), itemIndex, i, items[i].GetText().c_str());
    }

    wxString srcFile = m_srcDir + wxT("\\") + items[0].GetText();
    wxString dstFile = m_dstDir + wxT("\\") + items[2].GetText();

    const wxColor * bgColour = NULL;
    if(items[2].GetText().empty())
    {
        //items[2].SetText(items[0].GetText());
        items[1].SetText(wxT("新增->"));
        bgColour = wxGREEN;
        hasChange = true;
    }
    else if(items[0].GetText().empty())
    {
        items[1].SetText(wxT("删除"));
        bgColour = wxRED;
        hasChange = true;
    }
    else if(!SameFile(srcFile, dstFile))
    {
        items[1].SetText(wxT("覆盖->"));
        bgColour = wxCYAN;
        hasChange = true;
    }

    for(wxListItem & item: items)
    {
        if(bgColour) item.SetBackgroundColour(*bgColour);
        SetItem(item);
    }

    return hasChange;
}

inline bool IsOneFileOneDir(const wxString & src, const wxString & dst)
{
    return ( wxFileExists(src) && wxDirExists(dst) ) ||
            ( wxDirExists(src) && wxFileExists(dst) );
}

bool FileDiffList::SameFile(const wxString& src, const wxString& dst)
{
    if( IsOneFileOneDir(src, dst) )
    {
        wxLogMessage(wxT("file can't match a directory!"));
        return false;
    }
    else if( wxDirExists(src) && wxDirExists(dst) ) return true;

    wxFile srcFile(src);
    wxFile dstFile(dst.c_str());
    if(srcFile.Length() != dstFile.Length())
    {
        wxLogMessage(wxT("Diff %d, %s, %s, %d -> %d"), __LINE__, src.c_str(), dst.c_str(), (int)srcFile.Length(), (int)dstFile.Length());
        return false;
    }

    const size_t DELTA_SIZE = 1024*4;
    if(srcFile.Length() < DELTA_SIZE)
    {
        char buff[2][1024*5];
        int len1 = srcFile.Read(buff[0], srcFile.Length());
        int len2 = dstFile.Read(buff[1], dstFile.Length());
        if(len1 != len2)
        {
            //wxLogMessage(wxT("Diff %d"), __LINE__);
            return false;
        }
        if(strncmp(buff[0], buff[1], len1) != 0)
        {
            //wxLogMessage(wxT("Diff %d"), __LINE__);
            return false;
        }
    }
    else
    {
        char buff[2][1024*5];
        int len1 = srcFile.Read(buff[0], DELTA_SIZE);
        int len2 = dstFile.Read(buff[1], DELTA_SIZE);
        if(len1 != len2)
        {
            wxLogMessage(wxT("Diff %d"), __LINE__);
            return false;
        }
        if(strncmp(buff[0], buff[1], len1) != 0)
        {
            wxLogMessage(wxT("Diff %d"), __LINE__);
            return false;
        }
    }
    return true;
}


void FileDiffList::Resize(const wxSize& size)
{
    SetColumnWidth(1, 50);
    SetColumnWidth(0, size.GetWidth()/2 - GetColumnWidth(1));
    SetColumnWidth(2, size.GetWidth()/2 - GetColumnWidth(1)/2);
}

int FileDiffList::Sync(bool needDelete)
{
    if(wxThreadHelper::Create() != wxTHREAD_NO_ERROR)
    {
        wxMessageBox(wxT("已经在同步……淡定不要急……"));
        return 0;
    }
    m_needDelete = needDelete;
    if(GetThread()->Run() != wxTHREAD_NO_ERROR)
    {
        wxLogError(wxT("线程启动失败！\n"));
        return -2;
    }

    return 0;
}

wxThread::ExitCode FileDiffList::Entry()
{
    wxArrayString dirToDelete;
    for(long itemIndex=0; itemIndex<GetItemCount(); ++itemIndex)
    {
        wxListItem items[3];
        for(int i=0; i < 3; ++i)
        {
            items[i].SetId(itemIndex);
            items[i].SetColumn(i);
            items[i].SetMask(wxLIST_MASK_TEXT);
            if(!GetItem(items[i]))
            {
                //wxLogMessage(wxT("Sync: GetItem (%ld, %ld) fail"), itemIndex, i);
            }
            //wxLogMessage(wxT("Sync: Item(%ld, %ld): %s"), itemIndex, i, items[i].GetText().c_str());
        }
        wxString srcFile = m_srcDir + wxT("\\") + items[0].GetText();
        wxString dstFile = m_dstDir + wxT("\\") + items[0].GetText();
        wxCommandEvent event( wxEVT_SYNC_ONE, GetId() );
        event.SetEventObject( this );
        if(items[2].GetText().empty())//新增
        {
            event.SetString( wxString::Format(wxT("新增 %s -> %s"),
                                            srcFile.c_str(), dstFile.c_str()) );
            GetEventHandler()->ProcessEvent( event );

            wxString dir = wxFileName(dstFile).GetPath();
            wxFileName::Mkdir(dir, 0777, wxPATH_MKDIR_FULL);
            if( wxFileExists(srcFile) )
            {
                wxFileOutputStream out(dstFile);
                wxFileInputStream in(srcFile);
                out.Write(in);
            }
            else if( wxDirExists(srcFile) && !wxDirExists(dstFile) )
            {
                wxFileName::Mkdir(dstFile, 0777, wxPATH_MKDIR_FULL);
            }
        }
        else if(items[0].GetText().empty())
        {
            if(m_needDelete)
            {
                dstFile = m_dstDir + wxT("\\") + items[2].GetText();
                event.SetString(wxString::Format(wxT("删除 %s"), dstFile.c_str()) );
                if( wxFileExists(dstFile) )
                {
                    wxRemoveFile(dstFile);
                }
                else if( wxDirExists(dstFile) )
                {
                    dirToDelete.Add(dstFile);
                }
            }
            GetEventHandler()->ProcessEvent( event );
        }
        else if( GetItemBackgroundColour(itemIndex) == *wxCYAN )//覆盖
        {

            event.SetString( wxString::Format(wxT("覆盖 %s -> %s"),
            srcFile.c_str(), dstFile.c_str()) );
            GetEventHandler()->ProcessEvent( event );
            if( IsOneFileOneDir(srcFile, dstFile) )
            {
                wxLogError(wxT("文件冲突！一个是文件夹，一个是文件！%s, %s"), srcFile.c_str(), dstFile.c_str());
                continue;
            }

            wxFileOutputStream out(dstFile);
            wxFileInputStream in(srcFile);
            out.Write(in);
        }
        else
        {
            GetEventHandler()->ProcessEvent( event );
        }
    }
    for( size_t i = 0; i < dirToDelete.Count(); ++i ) wxRmdir(dirToDelete[i]);
    wxCommandEvent event( wxEVT_SYNC_ONE, GetId() );
    event.SetEventObject( this );
    GetEventHandler()->ProcessEvent( event );
    return 0;
}

